import json, random, string, traceback
import os, sys, ctypes
from typing import Any, Dict, NamedTuple, List
from threading import Thread
from functools import partial
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

# 获取当前正在运行的python位置
def get_running_python_path():
    for path in sys.path:
        str_Lib_index = path.lower().find('lib')
        if str_Lib_index > 0:
            return path[0: str_Lib_index - 1]

# 确定运行中的python是哪一个，供其他程序使用同一个
PYTHON_PATH = os.path.join(get_running_python_path(), 'python')
print(f'运行中Python: {PYTHON_PATH}')

# 程序的基本路径
BASE_DIR_PATH = os.path.join(os.getenv("HOME"), '.shawarma')
if not os.path.exists(BASE_DIR_PATH):
    os.makedirs(BASE_DIR_PATH)

# PID 文件的存放位置
PID_FILE_PATH = os.path.join(BASE_DIR_PATH, 'PID.txt')

# 程序临时文件存放位置
TMP_DIR_PATH = os.path.join(BASE_DIR_PATH, 'tmp')

# 执行任务记录
class ExecRecord(NamedTuple):
    id: str
    time: datetime
    result: str

# 运行服务记录
class ProgressRecord(NamedTuple):
    id: str
    time: datetime
    process: Thread

# 带显示名的数据
class TitledData(NamedTuple):
    # 显示名
    title: str
    # 编程用名
    name: str
    # 数据
    value: Any

# 获取本地Python信息
def get_python_info():
    import site
    sitepackages = site.getsitepackages()
    for item in sitepackages:
        item = item.replace(r'\\', r'\\')

    import pkg_resources

    installed_packages = pkg_resources.working_set

    # for package in installed_packages:
    #     print(package.key, package.version)
    return {
        'success': True,
        'message': [
            TitledData('可执行文件位置', 'executable_path', PYTHON_PATH)._asdict(),
            TitledData('第三方库位置', 'sitepackages_path', sitepackages)._asdict(),
            TitledData('第三方库', 'installed_packages', [{
                'key': x.key,
                'version': x.version
            } for x in installed_packages])._asdict(),
        ]
    }

class MyHTTPRequestHandler(BaseHTTPRequestHandler):

    def send_data(self, data: any):

        # common header
        # 200 OK
        self.send_response(200)
        # cors
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Headers', '*')

        # dict responce
        if isinstance(data, dict):
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps(data, ensure_ascii=False).encode())

        # other responce
        if isinstance(data, str):
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.wfile.write(str(data).encode())

    def makesure_tmp(self):
        # make sure tmp dir exist
        if not os.path.exists(TMP_DIR_PATH):
            os.makedirs(TMP_DIR_PATH)

    exec_records: List[ExecRecord] = []

    def server_list_exec_records(self):
        self.send_data({
            'records': self.exec_records
        })

    def server_exec(self, data):
        code = data['code']
        self.makesure_tmp()

        CELL_NAME = ''.join(random.choice(string.ascii_letters) for _ in range(8))
        PYFILE_PATH = f'{TMP_DIR_PATH}/{CELL_NAME}.py'
        with open(PYFILE_PATH, 'w', encoding='utf-8') as f:
            f.write(code)

        import importlib.util
        MODULE_PATH = PYFILE_PATH
        MODULE_NAME = CELL_NAME
        spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
        modulevar = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(modulevar)

        task = modulevar.task
        pool = ThreadPoolExecutor()
        future = pool.submit(task)
        success_message = future.result()
        try:
            logs = modulevar.logs
        except:
            logs = []

        self.exec_records.append(ExecRecord(CELL_NAME, datetime.now(), success_message))
        
        self.send_data({
            'success': True,
            'message': json.dumps({
                'value': success_message,
                'logs': logs,
            }, ensure_ascii=False),
        })

    progress_record: List[ProgressRecord] = []

    def server_list_process(self):
        self.send_data({
            'process': self.progress_record
        })

    def server_start_process(self, data):
        code = data['code']
        self.makesure_tmp()

        CELL_NAME = ''.join(random.choice(string.ascii_letters) for _ in range(8))
        PYFILE_PATH = f'{TMP_DIR_PATH}/{CELL_NAME}.py'
        LOGFILE_PATH = f'{TMP_DIR_PATH}/{CELL_NAME}.log'
        with open(PYFILE_PATH, 'w', encoding='utf-8') as f:
            f.write(code)
        
        
        import importlib.util
        MODULE_PATH = PYFILE_PATH
        MODULE_NAME = CELL_NAME
        spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
        modulevar = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(modulevar)

        process__ = modulevar.process__
        process__.start()

        self.progress_record.append(ProgressRecord(CELL_NAME, datetime.now(), process__))
        
        self.send_data({
            'success': True,
            'message': CELL_NAME
        })

    def server_stop_process(self, data):
        cell = data['cell']
        for record in self.progress_record:
            if record.id == cell:
                index = self.progress_record.index(record)
                record.process.stop()
                del self.progress_record[index]
                self.send_data({
                    'success': True,
                })
                break
        

    def do_GET(self):
        self.send_data(get_python_info())

    def do_OPTIONS(self):
        self.send_data('')

    def do_POST(self):
        try:
            data = self.rfile.read(int(self.headers['content-length']))
            data = json.loads(data)
            if self.path == '/server_exec':
                self.server_exec(data)
            elif self.path == '/server_start_process':
                self.server_start_process(data)
            elif self.path == '/server_stop_process':
                self.server_stop_process(data)
            else:
                self.send_response(404)
                self.wfile.write(b'404 not found')
        except Exception as e:
            print(e)
            traceback.print_exc()
            self.send_data({
                'success': False,
                'message': str(e),
            })

def cmd_serve():
    if os.path.exists(PID_FILE_PATH):
        print('服务已经启动，请勿重复启动。')
        input()
        return
    ppid = os.getpid()
    with open(PID_FILE_PATH, 'w') as f:
        f.write(str(ppid))

    with ThreadingHTTPServer(('127.0.0.1', 31828), MyHTTPRequestHandler) as httpd:
        try:
            print('Listern on http://127.0.0.1:31828/ ...')
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("\nKeyboard interrupt received, exiting.")
            os.remove(PID_FILE_PATH)
            sys.exit(0)

def _init_host():
    HOSTS_FILE_PATH = f"{os.getenv('windir')}\\System32\\Drivers\\etc\\hosts"
    needModify = False
    with open(HOSTS_FILE_PATH, 'r', encoding='utf-8') as f:
        content = f.read()
        if content.find('PythonHelper') < 0:
            needModify = True
        else:
            print('PythonHelper Host: 已修改')
    if needModify:
        with open(HOSTS_FILE_PATH, 'a', encoding='utf-8') as f:
            f.writelines([
                '\n'
                '# PythonHelper\n',
                '127.0.0.1 host.python-helper.internal\n'
            ])
        print('PythonHelper Host: 修改成功')
    input()

# 废弃：经常会与长长的环境变量冲突
# def _init_env():
#     if os.environ['PATH'].find('PythonHelper') >= 0:
#         print('PythonHelper Env: 已修改')
#     else:
#         os.system(f'setx path "%path%;%APPDATA%\\PythonHelper" /M')
#         print('PythonHelper Env: 修改成功')

def _init_service():
    content = '@echo off\nif "%1" == "h" goto begin\nmshta vbscript:createobject("wscript.shell").run("""%~0"" h",0)(window.close)&&exit\n:begincd %APPDATA%\\PythonHelper\npython python-helper.py serve'
    STARTUP_DIR_PATH = f"{os.getenv('appdata')}\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\start-python-helper.bat"
    with open(STARTUP_DIR_PATH, 'w', encoding='utf-8') as f:
        f.write(content)

def cmd_start():
    if os.path.exists(PID_FILE_PATH):
        print('cmd_start: 服务已经启动')
        return
    ctypes.windll.shell32.ShellExecuteW(None, "open", 'python', f'{__file__} serve', None, 0)

def cmd_stop():
    if not os.path.exists(PID_FILE_PATH):
        print('cmd_stop: 服务未启动')
        return
    with open(PID_FILE_PATH) as f:
        pid = int(f.read())
        os.system(f'taskkill /f /pid {pid}')
    os.remove(PID_FILE_PATH)

def cmd_restart():
    cmd_stop()
    import time
    time.sleep(0.5)
    cmd_start()

def cmd_init():
    if not ctypes.windll.shell32.IsUserAnAdmin():
        print(__file__)
        ctypes.windll.shell32.ShellExecuteW(None, "runas", 'python', f'{__file__} init', None, 1)
    else:
        _init_host()
        _init_service()
    input()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('无指令')
        exit()
    cmd = sys.argv[1]

    if cmd == 'init':
        cmd_init()
    
    if cmd == 'serve':
        cmd_serve()

    if cmd == 'start':
        cmd_start()
    
    if cmd == 'stop':
        cmd_stop()

    if cmd == 'restart':
        cmd_restart()


# 安装后
# 工作目录 %APPDATA%/PythonHelper
# 运行：
# - CMD
#   - start: `python %APPDATA%/PythonHelper/python-helper.py start`

# - SHELL
#   - start: `python $APPDATA/PythonHelper/python-helper.py start`

